package edu.unika.aifb.graphindex.searcher.hybrid.exploration;

/**
 * Copyright (C) 2009 G�nter Ladwig (gla at aifb.uni-karlsruhe.de)
 * 
 * This file is part of the graphindex project.
 *
 * graphindex is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2
 * as published by the Free Software Foundation.
 * 
 * graphindex is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with graphindex.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import edu.unika.aifb.graphindex.data.Table;
import edu.unika.aifb.graphindex.index.StructureIndex;
import edu.unika.aifb.graphindex.query.HybridQuery;
import edu.unika.aifb.graphindex.query.QNode;
import edu.unika.aifb.graphindex.searcher.hybrid.HybridQueryEvaluator;
import edu.unika.aifb.graphindex.searcher.keyword.KeywordSearcher;
import edu.unika.aifb.graphindex.searcher.keyword.model.KeywordElement;
import edu.unika.aifb.graphindex.searcher.keyword.model.KeywordSegment;
import edu.unika.aifb.graphindex.searcher.structured.TranslatedQueryEvaluator;
import edu.unika.aifb.graphindex.searcher.structured.VPEvaluator;
import edu.unika.aifb.graphindex.storage.StorageException;
import edu.unika.aifb.graphindex.util.Counters;
import edu.unika.aifb.graphindex.util.Timings;

public class ExploringHybridQueryEvaluator extends HybridQueryEvaluator {
	private StructureIndex m_si;
	private VPEvaluator m_eval;
	private ExploringIndexMatcher m_matcher;
	private KeywordSearcher m_searcher;
	private TranslatedQueryEvaluator m_tqe;
	
	private static final int MAX_INTERPRETATIONS = 50;

	private static final Logger log = Logger.getLogger(ExploringHybridQueryEvaluator.class);
	
	public ExploringHybridQueryEvaluator(edu.unika.aifb.graphindex.index.IndexReader idxReader) throws IOException, StorageException {
		super(idxReader);
		
		m_si = idxReader.getStructureIndex();
		m_eval = new VPEvaluator(idxReader);
		m_matcher = new ExploringIndexMatcher(idxReader);
		m_matcher.initialize();
		m_searcher = new KeywordSearcher(idxReader);
		m_tqe = new TranslatedQueryEvaluator(idxReader);
	}
	
	protected Map<KeywordSegment,Collection<KeywordElement>> search(String query, KeywordSearcher searcher, Timings timings) throws StorageException, IOException {
		List<String> list = KeywordSearcher.getKeywordList(query);
		log.debug("keyword list: " + list);
		Map<KeywordSegment,Collection<KeywordElement>> res = searcher.searchKeywordElements(list);
		return res;
	}

	
	protected void explore(HybridQuery query, int k, Map<KeywordSegment,Collection<KeywordElement>> segment2elements, ExploringIndexMatcher matcher, 
			List<TranslatedQuery> queries, Timings timings, Counters counters) throws StorageException, IOException {

		double inMax = 0.0, outMax = 0.0;
		Map<String,Double> inprops = new HashMap<String,Double>(), outprops = new HashMap<String,Double>();
		for (KeywordSegment ks : segment2elements.keySet()) {
			for (KeywordElement ele : segment2elements.get(ks)) {
				for (String property : ele.getInPropertyWeights().keySet()) {
					Double w = inprops.get(property) == null ? 0.0 : inprops.get(property);
					w += ele.getInPropertyWeights().get(property);
					inMax = Math.max(inMax, w);
					inprops.put(property, w);
				}
				
				for (String property : ele.getOutPropertyWeights().keySet()) {
					Double w = outprops.get(property) == null ? 0.0 : outprops.get(property);
					w += ele.getOutPropertyWeights().get(property);
					outMax = Math.max(outMax, w);
					outprops.put(property, w);
				}
			}
		}
		
		for (KeywordSegment ks : segment2elements.keySet()) {
			for (KeywordElement ele : segment2elements.get(ks)) {
				for (String property : ele.getInPropertyWeights().keySet())
					ele.getInPropertyWeights().put(property, inprops.get(property) / inMax);
				
				for (String property : ele.getOutPropertyWeights().keySet())
					ele.getOutPropertyWeights().put(property, outprops.get(property) / outMax);
			}
		}

		Map<String,Set<QNode>> ext2var = new HashMap<String,Set<QNode>>();
		
		if (k == 1)
			k = 3;
		matcher.setKeywords(segment2elements);
		matcher.setK(Math.min(k, MAX_INTERPRETATIONS));
		matcher.match();
		
		queries.addAll(matcher.indexMatches(query.getStructuredQuery(), ext2var));
		log.debug("queries: " + queries.size());
	}
	
	public Table<String> evaluate(HybridQuery query) throws StorageException, IOException {
		List<TranslatedQuery> queries = evaluate(query, 5, 5);
		if (queries.size() > 0 && queries.get(0) != null)
			return queries.get(0).getResult();
		else
			return new Table<String>();
	}

	@Override
	public List<TranslatedQuery> evaluate(HybridQuery query, int numberOfQueries, int queryResults) throws StorageException, IOException {
		Timings timings = new Timings();
		Counters counters = new Counters();
		if (numberOfQueries < 0)
			numberOfQueries = MAX_INTERPRETATIONS;
		if (queryResults < 0)
			queryResults = MAX_INTERPRETATIONS;
		
		log.info("evaluating...");
		log.debug("HybridQuery " + query);
		timings.start(Timings.TOTAL_QUERY_EVAL);
		Map<KeywordSegment,Collection<KeywordElement>> segment2elements = search(query.getKeywordQuery().getQuery(), m_searcher, timings);
		
		for(Collection<KeywordElement> keywordElements : segment2elements.values())
			for(KeywordElement keywordElement : keywordElements)
				log.debug(keywordElement.getUri() + " " + keywordElement.getKeywordSegment() + " " + keywordElement.entities.size());
		
		List<TranslatedQuery> queries = new ArrayList<TranslatedQuery>();
		
		explore(query, numberOfQueries, segment2elements, m_matcher, queries,  timings, counters);

		
//		timings.start(Timings.STEP_IQA);
//		
//		counters.set(Counters.QT_QUERIES, queries.size());
//		
//		numberOfQueries = Math.min(numberOfQueries, queries.size());
		
//		EntropyModel entropyModel = new EntropyModel(queryRelevanceModel, dbIndexStorage);
//		List<TranslatedQuery> rankedTranslatedQueries = new ArrayList<TranslatedQuery>();
//		for (int i = 0; i < numberOfQueries; i++) {
//			log.debug("------- query " + i + "/" + queries.size());
//			TranslatedQuery translated = queries.get(i);
//			counters.set(Counters.QT_QUERY_EDGES, translated.getQueryGraph().edgeCount());
//			log.debug(translated);
//
//			if (i < queryResults) {
//				Table<String> res = m_tqe.evaluate(translated);
//				if (res != null && res.rowCount() != 0){
//					translated.setResult(res);
//					System.out.println(res.toDataString());
//					List<String> resultEntities = new ArrayList<String>(); 
//					for(int j = 0 ; j < res.rowCount(); j++){
//						String[] row = res.getRow(j);
//						for(int k = 0 ; k < row.length; k++){
//							resultEntities.add(row[k]);
//						}
//					}
//					RelevanceModel structuredQueryRelevanceModel = new RelevanceModel(dbIndexStorage, resultEntities);
//					structuredQueryRelevanceModel.calculateRelevanceModel();
//					translated.setEntropy(entropyModel.calculateEntropyToDocument(structuredQueryRelevanceModel));
//					rankedTranslatedQueries.add(translated);
//					log.debug("Structured Query " + i +"entropy " + translated.getEntropy());
//				}
//			}
//		}
		
//		Collections.sort(rankedTranslatedQueries, new TranslatedQueryComparator<TranslatedQuery>());
		
		timings.end(Timings.STEP_IQA);
		
		timings.end(Timings.TOTAL_QUERY_EVAL);

		return queries;
	}
}

class TranslatedQueryComparator<T> implements Comparator<TranslatedQuery>{

	@Override
	public int compare(TranslatedQuery query1, TranslatedQuery query2) {
		if(query1.getEntropy() > query2.getEntropy())
			return 1;
		if(query1.getEntropy() < query2.getEntropy())
			return -1;
		return 0;
	}
	
}
